#region Copyright Notice
// 
// 
// Copyright (c) 2009-2010 Anindya Chatterjee
// 
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// 
//     http://www.apache.org/licenses/LICENSE-2.0
// 
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// 
// 
// 
#endregion
using System;
using System.IO;
using System.Net;
using System.Serialization;
using System.Threading;
using GCore.Authentication;

namespace GCore
{
    /// <summary>
    /// Base Google Service class. This class generically gets all the feeds, inserts new feed entry, queries
    /// using parameters. All Google service class must be derived from this class. The member functions are
    /// only visible to the class which inherits this class.
    /// </summary>
    /// <typeparam name="TFeed">The type of the atom feed.</typeparam>
    /// <typeparam name="TEntry">The type of the atom feed entry.</typeparam>
    public partial class GService<TFeed, TEntry>
        where TFeed : GFeed<TEntry>, new()
        where TEntry : GEntry, new()
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="GService&lt;TFeed, TEntry&gt;"/> class.
        /// </summary>
        public GService()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="GService&lt;TFeed, TEntry&gt;"/> class.
        /// </summary>
        /// <param name="userName">The Google account username.</param>
        /// <param name="password">The Google account password.</param>
        public GService(string userName, string password)
        {
            GUserName = userName;
            GPassword = password;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="GService&lt;TFeed, TEntry&gt;"/> class.
        /// </summary>
        /// <param name="userName">The Google account username.</param>
        /// <param name="password">The Google account password.</param>
        /// <param name="proxy">Webproxy settings, if the application is behind a firewall.</param>
        public GService(string userName, string password, WebProxy proxy)
            : this(userName, password)
        {
            ProxySettings = proxy;
        }

        /// <summary>
        /// Gets and sets the proxy settings for the application.
        /// </summary>
        public WebProxy ProxySettings { get; set; }

        /// <summary>
        /// Gets and sets the Google account username.
        /// </summary>
        public string GUserName { get; set; }

        /// <summary>
        /// Gets and sets the Google account password.
        /// </summary>
        public string GPassword { protected get; set; }

        /// <summary>
        /// Gets and sets the google feeds url.
        /// </summary>
        protected string GUrl { get; set; }

        /// <summary>
        /// Gets all feeds from <see cref="GUrl"/> specified for a particular Google service.
        /// </summary>
        /// <returns>
        /// Atom feed for the particular Google service.
        /// </returns>
        protected virtual TFeed GetAllFeed()
        {
            if (String.IsNullOrEmpty(GUrl))
                throw new ArgumentNullException(GUrl,
                                                "Feed Url is not provided. Please provide Feed url first through GUrl Property.");

            var request = WebRequest.Create(GUrl) as HttpWebRequest;

            if (request != null)
            {
                if (ProxySettings != null)
                    request.Proxy = ProxySettings;
                request.Credentials = new NetworkCredential(GUserName, GPassword);
                var webData = new GWebAsyncData<TFeed, TEntry>
                                  {Request = request, ResetEvent = new ManualResetEvent(false)};
                IAsyncResult result = request.BeginGetResponse(AsyncManager<TFeed, TEntry>.AsyncDownloadAllFeeds,
                                                               webData);
                ((GWebAsyncData<TFeed, TEntry>) result.AsyncState).ResetEvent.WaitOne();

                return ((GWebAsyncData<TFeed, TEntry>) result.AsyncState).FeedResult;
            }
            throw new OutOfMemoryException("Unable to create new WebRequest");
        }

        /// <summary>
        /// Submits a new atom entry to a particular Google service.
        /// </summary>
        /// <param name="entry">The atom entry object containing the data to submit.</param>
        /// <param name="applicationName">The name of the application which invokes this method.</param>
        /// <returns>
        /// A new atom entry containing some additional data like id, published date etc.
        /// </returns>
        protected virtual TEntry SubmitNewEntry(TEntry entry, string applicationName)
        {
            if (String.IsNullOrEmpty(GUrl))
                throw new ArgumentNullException(GUrl,
                                                "Feed Url is not provided. Please provide Feed url first through GUrl Property.");

            var request = WebRequest.Create(GUrl) as HttpWebRequest;

            if (request != null)
            {
                var serializer = new XObjectSerializer<TEntry>();
                string xml = serializer.StringSerialize(entry);
                long length = xml.ToCharArray().LongLength;

                var gc = new GDataCredentials(GUserName, GPassword)
                             {
                                 AccountType = "GOOGLE"
                             };
                string header = GAuthManager.RequestClientLoginHeader(gc, "code", applicationName, true,
                                                                      GoogleAuthentication.URI_HANDLER, ProxySettings);
                if (ProxySettings != null)
                    request.Proxy = ProxySettings;
                request.Method = "POST";
                request.Credentials = new NetworkCredential
                                          {
                                              UserName = GUserName,
                                              Password = GPassword
                                          };
                request.ContentType = "application/atom+xml";
                request.ContentLength = length;
                request.Headers.Add(header);

                var sw = new StreamWriter(request.GetRequestStream());
                sw.Write(xml);
                sw.Close();

                var webData = new GWebAsyncData<TFeed, TEntry>
                                  {Request = request, ResetEvent = new ManualResetEvent(false)};
                IAsyncResult result = request.BeginGetResponse(AsyncManager<TFeed, TEntry>.AsyncSubmitNewEntry, webData);

                ((GWebAsyncData<TFeed, TEntry>) result.AsyncState).ResetEvent.WaitOne();
                return ((GWebAsyncData<TFeed, TEntry>) result.AsyncState).EntryResult;
            }
            throw new OutOfMemoryException("Unable to create new WebRequest");
        }

        /// <summary>
        /// Queries a particular Google service using query parameters.
        /// </summary>
        /// <param name="query"><see cref="IGQuery"/> object, filled with data.</param>
        /// <returns>
        /// An atom feed for the particular Google service.
        /// </returns>
        protected virtual TFeed Query(IGQuery query)
        {
            if (String.IsNullOrEmpty(GUrl))
                throw new ArgumentNullException(GUrl,
                                                "Feed Url is not provided. Please provide Feed url first through GUrl Property.");

            string queryUrl = GUrl + "?" + query.GetQueryUrl();
            var queryUri = new Uri(queryUrl);
            var request = WebRequest.Create(queryUri) as HttpWebRequest;

            if (request != null)
            {
                if (ProxySettings != null)
                    request.Proxy = ProxySettings;
                request.Credentials = new NetworkCredential(GUserName, GPassword);
                var webData = new GWebAsyncData<TFeed, TEntry>
                                  {Request = request, ResetEvent = new ManualResetEvent(false)};
                IAsyncResult result = request.BeginGetResponse(AsyncManager<TFeed, TEntry>.AsyncDownloadAllFeeds,
                                                               webData);
                ((GWebAsyncData<TFeed, TEntry>) result.AsyncState).ResetEvent.WaitOne();

                return ((GWebAsyncData<TFeed, TEntry>) result.AsyncState).FeedResult;
            }
            throw new OutOfMemoryException("Unable to create new WebRequest");
        }
    }
}